'''
Created on Jan 14, 2011

@author: Jonathan Herbst
'''

import urllib, urllib2, re, time

class GoogleException(Exception): pass

class GCLBadAuthentication(GoogleException): pass
class GCLAccountProblem(GoogleException): pass
class GCLCaptchaRequired(GoogleException): pass

class C2DMQuotaExceded(GoogleException): pass
class C2DMDeviceQuotaExceded(GoogleException): pass
class C2DMInvalidRegistration(GoogleException): pass
class C2DMNotRegistered(GoogleException): pass
class C2DMMessageTooBig(GoogleException): pass
class C2DMMissingCollapseKey(GoogleException): pass

class ExponentialBackoff():
    
    def __init__(self):
        self.m_accumulatedBackoff = 0
        self.m_backoff = 1

    def _Backoff(self, exception, timeout):
        backoff = exception.info().getheader("Retry-After")
        backoff = 0 if backoff is None else int(backoff)
        
        self.m_backoff = min(max(self.m_backoff, backoff), self.m_accumulatedBackoff - timeout)
        if self.m_backoff <= 0:
            return False
        time.sleep(self.m_backoff)
        self.m_accumulatedBackoff += self.m_backoff
        self.m_backoff *= 2
        return True

class GoogleClientLogin(ExponentialBackoff):
    
    _errorToException = {"badauthentication": GCLBadAuthentication,
                         "notverified": GCLAccountProblem,
                         "termsnotagreed": GCLAccountProblem,
                         "captcharequired": GCLCaptchaRequired,
                         "accountdeleted": GCLAccountProblem,
                         "accountdisabled": GCLAccountProblem,
                         "servicedisabled": GCLAccountProblem}
    
    _url = "https://www.google.com/accounts/ClientLogin"
    _accountType = "GOOGLE"
    _source = "StructureEntryControl"
    _service = "ac2dm"
    
    _authTokenRE = re.compile("\nAuth=([\w\-]+)\n")
    
    _token = None
    
    def __init__(self, email, password):
        self.m_email = str(email)
        self.m_password = str(password)
        ExponentialBackoff.__init__(self)
        
    def getToken(self, force=False):
        
        if self._token is None or force:
            request = self._GetRequest()
            
            response = urllib2.urlopen(request)
            responseString = response.read()
            
            searchObj = self._authTokenRE.search(responseString)
            if searchObj is None:
                print responseString
                return None
            
            self._token = searchObj.group(1)
            return self._token
            
        return self._token
    
    def _GetToken(self, timeout=60):
        request = self._GetRequest()
        
        while True:
            try:
                response = urllib2.urlopen(request)
                return self._ParseAuthToken(response.read())
            except urllib2.HTTPError, e:
                if int(e.code) is 403:
                    error = str(e.info().getheader("Error")).lower()
                    if error in self._errorToException:
                        raise self._errorToException[error]()
                    if error is "serviceunavailable":
                        if not self._Backoff(e, timeout):
                            return False
                    else:
                        return False
    
    def _GetRequest(self):
        payload = {'accountType': self._accountType,
                   'Email': self.m_email,
                   'Passwd': self.m_password,
                   'source': self._source,
                   'service': self._service}
        
        return urllib2.Request(self._url, urllib.urlencode(payload))
    
    def _ParseAuthToken(self, responseString):
        searchObj = self._authTokenRE.search(responseString)
        if searchObj is None:
            print responseString
            return None
        
        return searchObj.group(1)
    
class GoogleC2DM(ExponentialBackoff):
    
    _errorToException = {"quotaexceded": C2DMQuotaExceded,
                         "devicequotaexceded": C2DMDeviceQuotaExceded,
                         "invalidregistration": C2DMInvalidRegistration,
                         "notregistered": C2DMNotRegistered,
                         "messagetoobig": C2DMMessageTooBig,
                         "missingcollapsekey": C2DMMissingCollapseKey}
    
    _url = "https://android.apis.google.com/c2dm/send"
    _collapseKey = 1
    _clientLogin = None
    
    def __init__(self, username, password, registrationId):
        self._clientLogin = GoogleClientLogin(username, password)
        self.m_registrationId = registrationId
        ExponentialBackoff.__init__(self)
        
    def SendMessage(self, message, timeout=60):
        messageSent = False
        request = self._GetRequest(message)
        
        while not messageSent:
            try:
                self._SendRequest(request)
                messageSent = True
            except urllib2.HTTPError, e:
                if int(e.code) is 503:
                    if not self._Backoff(e, timeout):
                        return False
                elif int(e.code) is 401:
                    try:
                        self._SendRequest(message, True)
                        messageSent = True
                    except urllib2.HTTPError, e:
                        if int(e.code) is 503:
                            if not self._Backoff(e, timeout):
                                return False
                        elif int(e.code) is 401:
                            return False # something is fucked if we are seeing this again
        return True

    def _GetRequest(self, message, forceNewToken=False):
        payload = {'registration_id': self.m_registrationId,
                   'collapse_key': self._collapseKey,
                   'data.message': str(message)}
        headers = {'Authorization': "GoogleLogin auth=" + self._clientLogin.getToken(forceNewToken)}
        
        return urllib2.Request(self._url, urllib.urlencode(payload), headers) 
        
    def _SendRequest(self, request):
        response = urllib2.urlopen(request)
        error = str(response.info().getheader("Error")).lower()
        if error in self._errorToException:
            raise self._errorToException[error]()
        self._collapseKey += 1
    
if __name__ == "__main__":
    clientLogin = GoogleClientLogin()
    print clientLogin.getToken()
    